﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

using Sampler;
using Shapes.Misc;
using Shapes.Geometry;
using Shapes.Misc.Appearance;

namespace TestGame.Samples
{
    /// <summary>
    /// This Sample shows a little jump'n'run game.
    /// The user plays a little smiley who can walk and jump from platform to platform.
    /// The goal is to collect as many golden rings as you can.
    /// The difficulty is a red line moving upwads which will kill you if it catches you.
    /// </summary>
    public class JumpNRunSample : Sample
    {

        #region Fields
        // Variables
        int _Score;
        bool _IsGameOver;

        // Misc
        TextureGenerator _Generator;
        CollisionDetector2D _Detector;
        Brush _DoomLineBrush, _RingBrush, _ObjectBrush;


        // the player has it's own class.
        TestGame.HelperClasses.JumpNRunPlayer _Player;

        // ...
        // Shapes & Drawings
        List<Ellipse> _Rings;   // the little rings to collect
        Line _DoomLine;         // the line which is going upwards

        // the platforms where the player can walk on
        List<Line> _Platforms;
        Polygon _Bottom;
        // the walls to the left and right
        Triangle _WallLeft, _WallRight;

        // ...
        // Textures
        Texture2D _RingTex;
        
        Texture2D _DoomLineTex;

        Texture2D _PlatformTex;
        Texture2D _BottomTex;
        Texture2D _WallTex;

        #endregion

        public JumpNRunSample(TextureGenerator generator)
            : base("")
        {
            _Generator = generator;
        }


        #region Initialize & DeActivate
        protected override void Initialize()
        {
            _Score = 0;
            _IsGameOver = false;
            SetText();
            
            _Detector = new CollisionDetector2D();
            _Detector.CollisionDetected += Detector_OnCollision;


            _RingBrush = Brush.CreateFallOffBrush(5, 3);
            _ObjectBrush = Brush.CreateFallOffBrush(2, 1);
            _DoomLineBrush = Brush.CreateFallOffBrush(10, 8);

            #region create rings
            // create 100 rings on a random position
            Random rand = new Random();

            _Rings = new List<Ellipse>();
            for (int i = 0; i < 100; i++)
            {
                Ellipse ring = new Ellipse(5, 5);
                ring.Center = new Vector2(40 + rand.Next(740), 120 + rand.Next(355));
                _Rings.Add(ring); // add ring to the list

                // add rings as passive geometry to the collision detector
                _Detector.SetupGeometry(ring, false, true);
            }
            _RingTex = _Generator.GeometryBorder(_Rings[0], _RingBrush);
            #endregion

            #region create player
            _Player = new TestGame.HelperClasses.JumpNRunPlayer(_Generator);
            _Player.Position = new Vector2(50, 480); 

            // add player to the collision manager as an active detector
            _Detector.SetupGeometry(_Player.Geometry, true, false);

            #endregion

            #region create bottom

            //// create the bottom polygon
            //List<Vector2> corners = new List<Vector2>();
            //corners.Add(new Vector2(0, 500));  // left top 
            //// between
            //corners.Add(new Vector2(200, 520));
            //corners.Add(new Vector2(300, 490));
            //corners.Add(new Vector2(500, 510));
            //// ...
            //corners.Add(new Vector2(800, 500)); // right top 
            //corners.Add(new Vector2(800, 600)); // right down
            //corners.Add(new Vector2(0, 600));  // left down

            _Bottom = new Polygon(new LineStrip(new Vector2(0, 500),
                new Vector2(200, 520), new Vector2(300, 490), new Vector2(500, 510),
                new Vector2(800, 500), new Vector2(800, 600), new Vector2(0, 600)
                )); // create shape

            // add bottom as passive geometry to the collision detector
            _Detector.SetupGeometry(_Bottom, false, true);

            // creat texture
            _BottomTex = _Generator.GeometryFilledWithBorder(_Bottom, Color.Black, _ObjectBrush);

            #endregion

            #region create walls
            // create the Walls on the left and right of the screen
            _WallLeft = new Triangle(Vector2.Zero, new Vector2(-20, 600), new Vector2(20, 600));
            _WallRight = (Triangle)_WallLeft.Clone(new Vector2(780, 0));

            // add walls as passive geometry to the collision detector
            _Detector.SetupGeometry(_WallLeft, false, true);
            _Detector.SetupGeometry(_WallRight, false, true);

            // one texture is enough for both walls
            _WallTex = _Generator.GeometryFilledWithBorder(_WallLeft, Color.Black, _ObjectBrush);

            #endregion

            #region createPlatforms

            // create Platform-Lines
            _Platforms = new List<Line>();

            // prefab is the line which is the prototype for all lines
            Line prefab = new Line(Vector2.Zero, new Vector2(200, 0));
            _PlatformTex = _Generator.GeometryBorder(prefab, _ObjectBrush);

            for (int i = 0; i < 3; i++)
            {
                // the platforms on the left
                Line l = (Line)prefab.Clone(new Vector2(0, 200 + i * 100));
                _Platforms.Add(l);
                _Detector.SetupGeometry(l, false, true);

                // the platforms in the right
                Line r = (Line)prefab.Clone(new Vector2(600, 200 + i * 100));
                _Platforms.Add(r);
                _Detector.SetupGeometry(r, false, true);
            }

            for (int i = 0; i < 4; i++)
            {
                // the platforms in the middle
                Line m = (Line)prefab.Clone();
                m.Center = new Vector2(400, 150 + i * 100);
                _Platforms.Add(m);
                _Detector.SetupGeometry(m, false, true);

            }

            #endregion

            #region create doom line
            // create the line which will going upwards
            _DoomLine = new Line(new Vector2(0, 600), new Vector2(800, 600));
            _DoomLineTex = _Generator.GeometryBorder(_DoomLine, _DoomLineBrush);

            _Detector.SetupGeometry(_DoomLine, false, true);
            #endregion
        }

        // release all references in this sample for a better performance in other samples
        protected override void DeActivate()
        {
            _Detector.Delete();
            _Detector = null;

            _DoomLineBrush = null;
            _ObjectBrush = null;
            _RingBrush = null;

            _Bottom.Dispose();
            _Bottom = null;
            _BottomTex = null;

            _DoomLine.Dispose();
            _DoomLine = null;
            _DoomLineTex = null;

            _Player.Dispose();
            _Player = null;

            while (_Rings.Count > 0)
            {
                _Rings[0].Dispose();
                _Rings.RemoveAt(0);
            }
            _Rings = null;
            _RingTex = null;

            _WallLeft.Dispose();
            _WallLeft = null;
            _WallRight.Dispose();
            _WallRight = null;
            _WallTex = null;

            base.DeActivate();
            // don't forget to call this, to make sure that Initialize() is called
            // when the user returns to this sample:
            ReInitializeOnNextActivation();
        }
        #endregion

        #region Update
        
        public override void Update(float seconds,
                                GamePadState currentButtons, GamePadState previousButtons,
                                KeyboardState currentKeys, KeyboardState previousKeys,
                                MouseState currentMouse, MouseState previousMouse)
        {

            if (!_IsGameOver)
            {
                // move the doom line upwards
                _DoomLine.Position = new Vector2(0, _DoomLine.Position.Y - seconds * 9);
                _Detector.CheckCollision();

                // Update the Player
                _Player.Update(seconds, currentButtons, currentKeys);

            }
            else // when game is over
            {
                if ((currentKeys.IsKeyDown(Keys.Enter) && previousKeys.IsKeyUp(Keys.Enter))
                    || (currentButtons.IsButtonDown(Buttons.Start) && previousButtons.IsButtonUp(Buttons.Start)))
                {
                    // restart
                    DeActivate();
                    Initialize();
                }
            }
        }
        #endregion

        #region Draw
        public override void Draw(SpriteBatch batch)
        {
            Game.GraphicsDevice.Clear(Color.DarkBlue);

            batch.Begin();

            // draw a Wall in the center of the screen... just for a better look and feel
            batch.Draw(_WallTex, new Vector2(380, 100), Color.Gray);

            // draw all platforms
            foreach (Line l in _Platforms)
            {
                batch.Draw(_PlatformTex, l.Position - _ObjectBrush.GetTextureOffset(), Color.White);
            }

            // draw all rings
            foreach (Ellipse ring in _Rings)
            {
                batch.Draw(_RingTex, ring.Position - _RingBrush.GetTextureOffset(), Color.Yellow);
            }

            // draw player (if he is alive)
            if (!_IsGameOver)
                _Player.Draw(batch);

            // draw bottom
            batch.Draw(_BottomTex, _Bottom.Position - _ObjectBrush.GetTextureOffset(), Color.White);

            // draw Doom Line
            batch.Draw(_DoomLineTex, _DoomLine.Position - _DoomLineBrush.GetTextureOffset(), Color.Red);

            // draw walls
            batch.Draw(_WallTex, _WallLeft.Position - _ObjectBrush.GetTextureOffset(), Color.White);
            batch.Draw(_WallTex, _WallRight.Position - _ObjectBrush.GetTextureOffset(), Color.White);


            batch.End();
        }
        #endregion

        #region other methods
        // due to displaying the current score and maybe "Game Over"
        // it is required to reset the description...
        void SetText()
        {
            Descripton = "Jump'n'run Sample:\n"
            + "Collect as many rings as you can until the red line is catching you.\n"
            + "Arrow right / left: go to the right / left\n"
            + "Space: Jump\n\n"
            + "Score: " + _Score.ToString();

            if (_IsGameOver)
            {
                Descripton += "\n\n       Game Over!\n"
                    + " Press return to restart";
            }
        }

        // this method is called when the collision detector has detected a collision
        void Detector_OnCollision(object sender, CollisionEventArgs args)
        {
            // on collision with a Ring -> pick it up and increase the score
            if (_Rings.Contains(args.PassiveGeometry as Ellipse))
            {
                _Detector.Remove(args.PassiveGeometry); // remove the ring from collision detection
                (args.PassiveGeometry as Ellipse).Dispose(); // release all references inside the ellipse
                _Rings.Remove(args.PassiveGeometry as Ellipse); // remove from the List of rings

                _Score++; // increase score
                SetText();
            }
            // on collision with the DoomLine -> Game Over!
            else if (args.PassiveGeometry == _DoomLine) 
            {
                _IsGameOver = true;
                SetText();
            }
            // on collision with a platform or the bottom -> stay on ground
            else if (args.PassiveGeometry == _Bottom || _Platforms.Contains(args.PassiveGeometry as Line))
            {
                _Player.CollideWithPlatform(args.PassiveGeometry);
            }
            // otherwise it must be a collision with a wall -> don't move
            else
            {
                _Player.CollideWithWall(args.PassiveGeometry);
            }
        }


        #endregion
    }
}
